package querier

import (
	"file-cleaner/internal/file_cleaner/pool"
	"file-cleaner/internal/file_cleaner/queue"
	"fmt"
	v1log "github.com/ClessLi/component-base/pkg/log/v1"
	"github.com/marmotedu/errors"
	"os"
	"path/filepath"
	"strings"
	"sync"
)

type FileQuerier struct {
	queryTaskPool *pool.Pool
	dirPathQueue  queue.Queue
	filepathQueue queue.Queue
}

func NewFileQuerier(poolCap int, filepathQueue queue.Queue) *FileQuerier {
	queryTaskPool, err := pool.NewPool(poolCap)
	if err != nil {
		panic(err)
	}
	return &FileQuerier{
		queryTaskPool: queryTaskPool,
		dirPathQueue:  queue.NewQueue(),
		filepathQueue: filepathQueue,
	}
}

func (q *FileQuerier) Query(dirPath string, cascade bool, excludes []string) error {
	absBaseDir, err := filepath.Abs(dirPath)
	if err != nil {
		return err
	}
	var absExcludePaths []string
	for _, exclude := range excludes {
		if filepath.IsAbs(exclude) {
			absExcludePaths = append(absExcludePaths, filepath.Clean(exclude))
		} else {
			absExcludePaths = append(absExcludePaths, filepath.Clean(filepathJoin(absBaseDir, exclude)))
		}
	}

	if isExcluded(filepath.Clean(absBaseDir), absExcludePaths) {
		return errors.New("the base directory for the query has been excluded")
	}

	// Check dirInfo
	dirInfo, err := os.Stat(absBaseDir)
	if err != nil {
		return err
	}

	if !dirInfo.IsDir() {
		return errors.Errorf("%v is not a directory", dirPath)
	}

	q.dirPathQueue.Add(&absBaseDir)

	err = q.query(cascade, absExcludePaths)
	if err != nil {
		return err
	}

	return nil
}

func isExcluded(targetPath string, excludes []string) bool {
	for _, exclude := range excludes {
		if strings.TrimSpace(targetPath) == strings.TrimSpace(exclude) {
			return true
		}
	}
	return false
}

func (q *FileQuerier) query(cascade bool, excludes []string) error {
	wg := new(sync.WaitGroup)

	for !q.dirPathQueue.IsEmpty() {
		item := q.dirPathQueue.Poll()
		dirPath, ok := item.(*string)
		if ok {
			wg.Add(1)
			err := q.queryTaskPool.Submit(func() {
				defer wg.Done()
				defer func() { *dirPath = "" }() // 释放string内存
				err := q.parseDir(dirPath, cascade, excludes)
				if err != nil {
					v1log.Warn(err.Error())
				}
			})
			if err != nil {
				v1log.Warn(err.Error())
			}
		}
		if q.dirPathQueue.IsEmpty() {
			wg.Wait()
		}
	}
	return nil
}

func (q *FileQuerier) parseDir(dirPath *string, cascade bool, excludes []string) error {
	tmpEntries, err := os.ReadDir(*dirPath)
	if err != nil {
		return fmt.Errorf("failed to read directory: %v, cased by: %v", dirPath, err)
	}
	defer func() { tmpEntries = tmpEntries[:] }()

	for _, entry := range tmpEntries {
		entryPath := filepathJoin(*dirPath, entry.Name())
		// exclude query
		if isExcluded(filepath.Clean(entryPath), excludes) {
			v1log.Infof("Query exclude: %s", filepath.Clean(entryPath))
			continue
		}

		if entry.IsDir() {
			if !cascade {
				continue
			}

			//subDirPath := filepath.Join(dirPath, entry.Name())
			//q.dirPathQueue.Add(subDirPath)
			q.dirPathQueue.Add(&entryPath)

		} else {
			//logger.Debugf("Catch file: %v", filepath.Join(dirPath, entry.Name()))
			v1log.Debugf("Catch file: %v", entryPath)
			q.filepathQueue.Add(&entryPath)
		}
	}

	return nil
}

func (q *FileQuerier) Done() {
	q.queryTaskPool.Wait()
	q.filepathQueue.Add(queue.Done)
}
